/*
    Copyright 2013 Jonathan Kaschak

    This file is part of TerraTamper.

    TerraTamper is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    TerraTamper is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with TerraTamper.  If not, see <http://www.gnu.org/licenses/>.
*/

package Main;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

class ProxyConnection extends Thread
{
  public ProxyConnection othergoing;
  public ProxyConnection localhost_socket;
  private Socket incoming;
  private Socket outgoing;
  private boolean isClient;
  private byte[] buffer;
  private byte[] bufferqueue = new byte[1024];
  private int numberRead;
  private int queueRead;
  private int tLength;
  
  private boolean killme = false;
  private int PlayerID;
  
  private int heal_timer = 0;
  private int heal_player_id = 0;
  
  private boolean buddha = false;
  private boolean loldynamite = false;
  private int bunnyiter = 1;
          
  private boolean god = false;
  private int god_id = 0;
  
  private boolean fly = false;
  private int fly_id = 0;
  
  private String deathtext = "";
  
  //private boolean debug = false;
  public boolean admin = true;
  public boolean localhost = false;
  OutputStream ToClient;
  InputStream FromClient;

  ProxyConnection(Socket in_incoming, Socket in_outgoing, boolean in_isClient) {
    incoming = in_incoming;
    outgoing = in_outgoing;
    isClient = in_isClient;
  }
  
    private String parseCommand(String message) {
        String[] parseme = message.substring(1).toLowerCase().split(" ");
        return parseme[0];
  } private String parseArgument(String message) {
        String[] parseme = message.substring(1).toLowerCase().split(" ");
        String arg = "";
        for(int i = 1; i < parseme.length; i++) {
            if(!parseme[i].matches("-?\\d+(\\.\\d+)?"))
                arg = arg + parseme[i] + " ";
        }
        if(arg.length() > 0) arg = arg.substring(0, arg.length() - 1);
        return arg;
  } private int parseNumber(String message) {
    try {
        String[] parseme = message.substring(1).toLowerCase().split(" ");
        int num = 0;
        for(int i = 1; i < parseme.length; i++) {
            if(parseme[i].matches("-?\\d+(\\.\\d+)?"))
                num = Integer.parseInt(parseme[i]);
        }
        return num;
    } catch (Exception ex) {
        
    }
    return 0;
  }
  
  private byte[] filter() {
    //buffer;
    
    //Handle all silent player database functions (that do not give command line output)
    if (TerrariaProtocol.getMessageType(buffer) == Packets.UpdatePlayer.packet_id) {
      PlayerDatabase.setPositionX(PlayerID, Packets.UpdatePlayer.GetPositionX(buffer));
      PlayerDatabase.setPositionY(PlayerID, Packets.UpdatePlayer.GetPositionY(buffer));
    }
    if (TerrariaProtocol.getMessageType(buffer) == Packets.PlayerInfo.packet_id) {
      PlayerID = Packets.PlayerInfo.GetPlayerID(buffer);
      PlayerDatabase.setName(PlayerID, Packets.PlayerInfo.GetName(buffer));
    }
    
    if (localhost){ //the localhost is the one with the cmd window.
        if(isClient){ //output packets sent to server
        } else { //output packets recieved from server
            if(TerrariaProtocol.getMessageType(buffer) == Packets.ChatMessage.packet_id) {
                if(Packets.ChatMessage.GetPlayerID(buffer)!=-1)
                    System.out.print("<" + PlayerDatabase.getName(Packets.ChatMessage.GetPlayerID(buffer)) + "> ");
                System.out.println(Packets.ChatMessage.GetMessage(buffer));
            }
        }
    }
    
    if (isClient) {
        /*if(TerrariaProtocol.getMessageType(buffer) == Packets.ProjectileUpdate.packet_id){
            System.out.print("buffer[] = {");
            for (int i = 0; i < numberRead; i++) {
              System.out.print(buffer[i] + ", ");
            }
            System.out.println("};");
            System.out.println("numberRead=" + numberRead + ";");
        }*/
        /*if(TerrariaProtocol.getMessageType(buffer) == Packets.ProjectileUpdate.packet_id){
            byte[] proj = Packets.DestroyProjectile.DestroyProjectile(
                    Packets.ProjectileUpdate.GetProjectileID(buffer),
                    Packets.ProjectileUpdate.GetOwner(buffer)
                );
            //System.out.println("type: " + Packets.ProjectileUpdate.GetType(buffer));
            //System.out.println("ai: " + Packets.ProjectileUpdate.GetAI(buffer));
            byte[] proj2 = Packets.ProjectileUpdate.CreateProjectile(
                    Packets.ProjectileUpdate.GetProjectileID(buffer), 
                    Packets.ProjectileUpdate.GetPositionX(buffer), 
                    Packets.ProjectileUpdate.GetPositionY(buffer), 
                    Packets.ProjectileUpdate.GetVelocityX(buffer), 
                    Packets.ProjectileUpdate.GetVelocityY(buffer), 
                    Packets.ProjectileUpdate.GetKnockback(buffer), 
                    Packets.ProjectileUpdate.GetDamage(buffer), 
                    Packets.ProjectileUpdate.GetOwner(buffer), 
                    111,//Packets.ProjectileUpdate.GetType(buffer),
                    0);//Packets.ProjectileUpdate.GetAI(buffer));
            buffer = TerrariaProtocol.dataadd(buffer, numberRead, proj, TerrariaProtocol.getMessageLength(proj));
            numberRead += TerrariaProtocol.getMessageLength(proj);
            buffer = TerrariaProtocol.dataadd(buffer, numberRead, proj2, TerrariaProtocol.getMessageLength(proj2));
            numberRead += TerrariaProtocol.getMessageLength(proj2);
            
            othergoing.bufferqueue = buffer;
            othergoing.queueRead = true;
        }*/
        if(TerrariaProtocol.getMessageType(buffer) == Packets.ChatMessage.packet_id) { //Handle chat commands
            if(Packets.ChatMessage.GetMessage(buffer).startsWith("/") || 
               Packets.ChatMessage.GetMessage(buffer).startsWith("\\")) {
                numberRead=0;
                int who_said_it = Packets.ChatMessage.GetPlayerID(buffer);
                switch(parseCommand(Packets.ChatMessage.GetMessage(buffer))){
                    case "item" : case "give" :  case "i" : case "g" :
                        String dropme = parseArgument(Packets.ChatMessage.GetMessage(buffer));
                        int dropid = ItemIDs.item(parseArgument(Packets.ChatMessage.GetMessage(buffer)));
                        int dropamount = parseNumber(Packets.ChatMessage.GetMessage(buffer));
                        if(dropamount==0) dropamount = 1;
                        if(dropamount>255) dropamount = 255;
                        if(dropid!=0) {
                            System.out.println("Dropping item: " + dropme);
                            byte[] drop = Packets.UpdateItemDrop.CreateDrop(
                                    0,
                                    PlayerDatabase.getPositionX(who_said_it),
                                    PlayerDatabase.getPositionY(who_said_it),
                                    0,
                                    0,
                                    dropamount,
                                    0,
                                    1,
                                    dropid
                                );
                            buffer = TerrariaProtocol.reconstruct(buffer, drop);
                            numberRead = TerrariaProtocol.getMessageLength(buffer);
                        } else {
                            System.out.println("Unknown item: " + dropme);
                        }
                        break;
                    case "heal" : case "healme" :
                        heal_timer = 40;
                        heal_player_id = who_said_it;
                        break;
                    case "buddha" : case "bud" : 
                        heal_player_id = who_said_it;
                        buddha = !buddha;
                        break;
                    case "god" :  
                        god_id = who_said_it;
                        god = !god;
                        break;
                    case "fly" :  
                        fly_id = who_said_it;
                        fly = !fly;
                        break;
                    case "buff" : 
                        String buffme = parseArgument(Packets.ChatMessage.GetMessage(buffer));
                        int buffid = DataIDs.buff(parseArgument(Packets.ChatMessage.GetMessage(buffer)));
                        int bufftime = parseNumber(Packets.ChatMessage.GetMessage(buffer));
                        if(bufftime==0) bufftime = 10;
                        bufftime *= 60;
                        if(buffid!=0) {
                            System.out.println("Gave player: " + buffme);
                            byte[] buff = Packets.AddPlayerBuff.AddPlayerBuff(
                                        who_said_it,
                                        buffid,
                                        bufftime
                                    );
                            buffer = TerrariaProtocol.reconstruct(buffer, buff);
                            numberRead = TerrariaProtocol.getMessageLength(buffer);
                        } else {
                            System.out.println("Unknown buff: " + buffme);
                        }
                        break;
                    case "loldynamite" : case "bunnybolt" : 
                        loldynamite = !loldynamite;
                        System.out.println("bunnybolt enabled (try using your magic missile!)");
                        break;
                    case "bunny" : case "bun" : 
                        byte[] bunny = Packets.AddPlayerBuff.AddPlayerBuff(
                                    who_said_it,
                                    40,
                                    20
                                );
                        buffer = TerrariaProtocol.reconstruct(buffer, bunny);
                        numberRead = TerrariaProtocol.getMessageLength(buffer);
                        break;
                    case "spawnboss" : case "boss" : 
                        System.out.println("Attempted to spawn a boss. NOTE: Make sure you are in a place where the boss can actually spawn!");
                        byte[] boss = Packets.SpawnBoss.SpawnBossInvasion(
                                    who_said_it,
                                    DataIDs.boss(parseArgument(Packets.ChatMessage.GetMessage(buffer)))
                                );
                        buffer = TerrariaProtocol.reconstruct(buffer, boss);
                        numberRead = TerrariaProtocol.getMessageLength(buffer);
                        break;
                    case "deathtext" : 
                        deathtext = parseArgument(Packets.ChatMessage.GetMessage(buffer));
                        break;
                    default:
                        System.out.println("Command not found: " + parseCommand(Packets.ChatMessage.GetMessage(buffer)));
                        break;
                }
            }
        }
        if(TerrariaProtocol.getMessageType(buffer) == Packets.KillMe.packet_id && deathtext.length()>0 && !"default".equals(deathtext)) {
            buffer = TerrariaProtocol.reconstruct(buffer, Packets.KillMe.SetDeathtext(buffer,deathtext));
            numberRead = TerrariaProtocol.getMessageLength(buffer);
        }
        //Handle command stuff
        if( 
                loldynamite && 
                TerrariaProtocol.getMessageType(buffer) == Packets.ProjectileUpdate.packet_id //&& 
                //Packets.ProjectileUpdate.GetType(buffer) == 16
          ){
            //first, destroy our non bunny magic missile
            byte[] proj = Packets.DestroyProjectile.DestroyProjectile(
                    Packets.ProjectileUpdate.GetProjectileID(buffer),
                    Packets.ProjectileUpdate.GetOwner(buffer)
                );
            bunnyiter++;
            if(bunnyiter>234)bunnyiter=1;
            byte[] proj2 = Packets.ProjectileUpdate.CreateProjectile(
                    bunnyiter, 
                    Packets.ProjectileUpdate.GetPositionX(buffer), 
                    Packets.ProjectileUpdate.GetPositionY(buffer), 
                    Packets.ProjectileUpdate.GetVelocityX(buffer), 
                    Packets.ProjectileUpdate.GetVelocityY(buffer), 
                    Packets.ProjectileUpdate.GetKnockback(buffer), 
                    Packets.ProjectileUpdate.GetDamage(buffer), 
                    Packets.ProjectileUpdate.GetOwner(buffer), 
                    29,//Packets.ProjectileUpdate.GetType(buffer),
                    0);//Packets.ProjectileUpdate.GetAI(buffer));
            //buffer = TerrariaProtocol.dataadd(buffer, numberRead, proj, TerrariaProtocol.getMessageLength(proj));
            //numberRead += TerrariaProtocol.getMessageLength(proj);
            buffer = TerrariaProtocol.dataadd(buffer, numberRead, proj2, TerrariaProtocol.getMessageLength(proj2));
            numberRead += TerrariaProtocol.getMessageLength(proj2);
            
            /*othergoing.bufferqueue = TerrariaProtocol.reconstruct(othergoing.bufferqueue, proj);
            othergoing.queueRead = TerrariaProtocol.getMessageLength(othergoing.bufferqueue);
            othergoing.bufferqueue = TerrariaProtocol.dataadd(othergoing.bufferqueue, othergoing.queueRead, proj2, TerrariaProtocol.getMessageLength(proj2));
            othergoing.queueRead += TerrariaProtocol.getMessageLength(proj2);*/
            othergoing.bufferqueue = TerrariaProtocol.reconstruct(othergoing.bufferqueue, proj2);
            othergoing.queueRead = TerrariaProtocol.getMessageLength(othergoing.bufferqueue);
        }
        //Handle timed commands
        if(heal_timer>0||buddha){
            heal_timer--;
            int dropid = 58;
            if ((heal_timer > 0) && (heal_timer % 2 == 0)) dropid = 184;
            byte[] drop = Packets.UpdateItemDrop.CreateDrop(
                    0,
                    PlayerDatabase.getPositionX(heal_player_id),
                    PlayerDatabase.getPositionY(heal_player_id),
                    0,
                    0,
                    1,
                    0,
                    1,
                    dropid
                );
            buffer = TerrariaProtocol.dataadd(buffer, numberRead, drop, TerrariaProtocol.getMessageLength(drop));
            numberRead += TerrariaProtocol.getMessageLength(drop);
        }
        if(god){
            int[] goodbuffs = {59, 3, 63, 9, 11, 12, 1};
            for(int i = 0; i < goodbuffs.length; i++){
                byte[] dat = Packets.AddPlayerBuff.AddPlayerBuff(
                            god_id,
                            goodbuffs[i],
                            300
                        );
            buffer = TerrariaProtocol.dataadd(buffer, numberRead, dat, TerrariaProtocol.getMessageLength(dat));
            numberRead += TerrariaProtocol.getMessageLength(dat);
            }
        }
        if(fly){
            int[] goodbuffs = {8, 18};
            for(int i = 0; i < goodbuffs.length; i++){
                byte[] dat = Packets.AddPlayerBuff.AddPlayerBuff(
                            god_id,
                            goodbuffs[i],
                            300
                        );
            buffer = TerrariaProtocol.dataadd(buffer, numberRead, dat, TerrariaProtocol.getMessageLength(dat));
            numberRead += TerrariaProtocol.getMessageLength(dat);
            }
        }
    }
    
    if(queueRead > 0){
        buffer = TerrariaProtocol.dataadd(buffer, numberRead, bufferqueue, queueRead);
        numberRead += queueRead;
        queueRead = 0;
    }
    
    return buffer;
  }

  @Override
  public void run() {
      buffer = new byte[1024];
      numberRead = 0;
      try {
            ToClient = outgoing.getOutputStream();
      } catch (IOException ex) {
          System.out.println("Error: Couldn't get output stream to client.");
      }
      try {
          FromClient = incoming.getInputStream();
      } catch (IOException ex) {
          System.out.println("Error: Couldn't get input stream from client.");
      }
      if(isClient)System.out.println("Client connected");
      while (!killme) {
        try {
            numberRead = FromClient.read(buffer, 0, buffer.length);
        } catch (IOException ex) {
            System.out.println("Error: Couldn't get stream buffer size.");
            killme = true;
        }
        tLength = (buffer[0] + 4);
        if (tLength == numberRead)
        {
          buffer = filter();
        }
        if (numberRead == -1) {
          killme = true;
          if(isClient)System.out.println("Client disconnected");
            try {
                incoming.close();
            } catch (IOException ex) {
                System.out.println("Error closeing incoming stream.");
            }
            try {
                outgoing.close();
            } catch (IOException ex) {
                System.out.println("Error closeing outgoing stream.");
            }
        } else {
            try {
                ToClient.write(buffer, 0, numberRead);
            } catch (IOException ex) {
                System.out.println("Error sending data.");
                killme = true;
            }
        }
      }
  }
}
